MoonBit v0.10で補間文字列が強化されまくった
#moonbit
https://www.moonbitlang.com/updates/
let r = "a\{b}c"の展開結果でStringBuilderを使うようになった
これまではナイーブに"a" + b.to_string() + "c"だった
わかりやすいけど非効率
これが以下のようになる
code:new.mbt
let r = {
let builder = StringBuilder(size_hint=2)
builder.write_string("a")
builder.write(b)
builder.write_string("c")
builder.to_string()
}
つまりStringBuilderを使った効率的な文字列組み立てに展開される
文字列補間に匿名関数を渡せるようになる
"a\{builder => builder.f()}c"という書き方ができるようになる
戻り値の型はUnitである必要がある
引数のbuilderは、上記の展開結果の変更により導入された暗黙のbuilderが渡ってくる
匿名関数でないとこの挙動にはならず、(builder) -> Unit型の顕名関数を渡してもwriteに解決される
StringBuilder関係の便利な演算子が生えた
lhs <+ rhs
右辺には文字列か辞書リテラル { "k1": v1, "k2": v2 }が渡せる
lhs.write_string(rhs)あるいはlhs.write(rhs)に展開される
たとえば、buf <+ "hoge"はbuf.write_string("hoge)"になる
右辺が文字列補間"a\{b}c"の場合、レシーバは暗黙のbuilderではなくbufが使われる
つまりbuf..write_string("a")..write(b)..write_string("c")になる
左辺にはStringBuilderだけでなく、T::write_string(T, String)とT::write(T, X)が定義されている任意の型Tが来れる
lhs <? rhs
右辺に来れる項は<+と同じ
左辺にはOption[T](Tは<+の左辺と同様のStringBuilder -likeな型)を持ってこれる
左辺がSome(x)の場合にのみx <+ rhsを実行する
単体だと"a\{builder => builder.f()}c"記法の嬉しさがわからなかったが、<+演算子および自前StringBuilderと組み合わせられると考えると色々使いどころがありそう
例
以下のHogeは上記の条件を満たすのでStringBuilderと同様の使い方ができる
code:sample.mbt
///|
fn main {
let hoge : Hoge = Hoge::new()
hoge <+ "aaa\{b => b.increment()}cccc\{123456789}"
println(hoge.build())
}
///|
struct Hoge {
x : Int
mut cnt : Int
}
///|
fn Hoge::new() -> Hoge {
{ x: 3, cnt: 0 }
}
///|
fnX Hoge::write(self : Hoge, _ : X) -> Unit {
println("call write")
self.cnt += self.x
}
///|
fn Hoge::write_string(self : Hoge, t : String) -> Unit {
println("call write_string")
self.cnt += t.length()
}
///|
fn Hoge::increment(self : Hoge) -> Unit {
println("call increment")
self.cnt += 1
}
///|
fn Hoge::build(self : Hoge) -> String {
self.cnt.to_string()
}
実行結果
code:result.txt
call write_string
call increment
call write_string
call write
11
その他
補間文字列の中で"をエスケープせずに書けるようになった
つまり「xsを";"でjoinした文字列の挿入」が"ab\{xs.join(";")}"みたいに書ける
地味にうれしい
追加検証
複数行文字列を渡した場合はまとめて1回のwrite_string呼び出しになる
code:multiline.mbt
hoge <+
#|aaa
#|bbb
#|ccc
// -> call write_string
複数行文字列に補間文字列行を混ぜても1個の<+にまとめて書けるが、#|と$|は別のwrite_string呼び出しに分割される
code:multiline_interpolation.mbt
hoge <+
#|aaa
$|bbb
#|ccc
// -> call write_string
// -> call write_string
// -> call write_string
code:multiline_interpolation.mbt
hoge <+
#|aaa
$|b\{0}b
#|ccc
// -> call write_string
// -> call write_string
// -> call write
// -> call write_string
// -> call write_string
writeが定義されていなくても、\{x}が型エラーになるだけで、文字列と\{b => b.f()}だけからなる補間文字列を受け付けるレシーバになる
write_stringが定義されていなくても、文字列がエラーになるだけで、リテラルを含まない補間文字列を受け付けるレシーバになる
なんならwriteもwrite_stringも定義されていなくても、\{b => b.f()}だけを受け付けるレシーバになる
きもすぎ
https://scrapbox.io/files/6a2997eb601c8b4efdd7b6f3.png